import os
import chardet
import time
import re
from typing import List, Dict, Tuple
from docx import Document
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate
from langchain_core.output_parsers.json import StructuredOutputParser
from langchain_core.output_parsers import ResponseSchema
from langchain_core.messages import HumanMessage, SystemMessage, AIMessage


class SmartExamBot:
    def __init__(self, api_key: str, base_url: str, max_questions: int = 5):
        """
        初始化考试机器人（基于LangChain）
        :param api_key: API密钥
        :param base_url: API基础URL
        :param max_questions: 最大题目数量
        """
        self.api_key = api_key
        self.base_url = base_url
        self.max_follow_ups = 3  # 每道题最大追问次数
        self.question_count = 0  # 题目计数
        self.total_score = 0  # 总分
        self.max_possible_score = 0  # 可能获得的最高分
        self.max_questions = max_questions  # 最大题目数量
        self.current_follow_ups = 0  # 当前题目的追问次数

        # 初始化LangChain大模型客户端
        self.llm = ChatOpenAI(
            api_key=api_key,
            base_url=base_url,
            model_name="deepseek-v3",
            temperature=0.3,  # 降低随机性，使评分更稳定
            timeout=30
        )

        # 定义评分响应格式
        self.response_schemas = [
            ResponseSchema(name="score", description="分数(0-10)"),
            ResponseSchema(name="feedback", description="具体反馈"),
            ResponseSchema(name="need_follow_up", description="是否追问(是/否)")
        ]
        self.output_parser = StructuredOutputParser.from_response_schemas(self.response_schemas)
        self.format_instructions = self.output_parser.get_format_instructions()

    def detect_encoding(self, file_path: str) -> str:
        """检测文件编码，仅用于文本文件"""
        if file_path.lower().endswith('.docx'):
            return 'docx'  # 特殊标记，表示docx文件
        try:
            with open(file_path, 'rb') as file:
                result = chardet.detect(file.read(10240))
                return result['encoding'] or 'utf-8'
        except Exception as e:
            raise Exception(f"编码检测失败: {str(e)}")

    def read_file_content(self, file_path: str) -> str:
        """读取文件内容，支持txt和docx格式"""
        # 验证文件存在性
        if not os.path.exists(file_path):
            raise FileNotFoundError(f"文件不存在: {file_path}")

        if not os.path.isfile(file_path):
            raise IsADirectoryError(f"{file_path}不是一个文件")

        try:
            encoding = self.detect_encoding(file_path)

            # 处理docx文件
            if encoding == 'docx':
                doc = Document(file_path)
                full_text = []

                # 提取段落文本
                for para in doc.paragraphs:
                    full_text.append(para.text)

                # 提取表格内容
                for table in doc.tables:
                    for row in table.rows:
                        row_text = [cell.text for cell in row.cells]
                        full_text.append('\t'.join(row_text))

                return '\n'.join(full_text)

            # 处理文本文件
            encodings_to_try = [encoding, 'utf-8', 'gbk', 'latin-1']
            for enc in encodings_to_try:
                try:
                    with open(file_path, 'r', encoding=enc, errors='replace') as file:
                        return file.read()
                except UnicodeDecodeError:
                    continue
            raise Exception("无法识别文件编码")
        except Exception as e:
            raise Exception(f"文件读取失败: {str(e)}")

    def get_question(self, knowledge_content: str, chat_history: List) -> str:
        """使用LangChain生成题目"""
        # 构建提示词模板
        prompt = ChatPromptTemplate.from_messages([
            SystemMessagePromptTemplate.from_template(
                "你是一个专业严谨的智能考试机器人，基于以下知识内容出题:\n{knowledge_content}\n"
                "请出一道较为简单的主观题，考察对知识的理解和应用能力，不要添加任何额外说明"
            ),
            *chat_history  # 加入对话历史
        ])

        # 创建链并执行
        chain = prompt | self.llm
        response = chain.invoke({"knowledge_content": knowledge_content})
        return response.content

    def evaluate_answer(self, knowledge_content: str, chat_history: List) -> Dict:
        """使用LangChain评估答案"""
        # 构建评分提示词模板
        prompt = ChatPromptTemplate.from_messages([
            SystemMessagePromptTemplate.from_template(
                "你是一个专业严谨的智能考试评分机器人，基于以下知识内容评分:\n{knowledge_content}\n"
                "评分标准:\n"
                "1. 答案准确性(0-4分)\n"
                "2. 回答完整性(0-3分)\n"
                "3. 逻辑清晰度(0-3分)\n"
                "请根据上述标准对用户的回答进行评分，并提供具体反馈。\n"
                "输出格式要求:\n{format_instructions}"
            ),
            *chat_history  # 加入对话历史
        ])

        # 创建链并执行
        chain = prompt | self.llm
        response = chain.invoke({
            "knowledge_content": knowledge_content,
            "format_instructions": self.format_instructions
        })

        # 解析结构化输出
        try:
            return self.output_parser.parse(response.content)
        except Exception as e:
            print(f"解析评分结果失败: {str(e)}")
            print(f"原始响应: {response.content}")
            # 返回默认值
            return {"score": 0, "feedback": "评分解析失败", "need_follow_up": "否"}

    def get_follow_up_question(self, chat_history: List) -> str:
        """使用LangChain生成追问问题"""
        prompt = ChatPromptTemplate.from_messages([
            SystemMessagePromptTemplate.from_template(
                "你是一个专业的考试机器人，需要基于之前的对话内容提出一个有针对性的追问问题，"
                "深入考察用户对知识点的理解程度。只需要返回问题本身，不要添加任何额外说明。"
            ),
            *chat_history  # 加入对话历史
        ])

        chain = prompt | self.llm
        response = chain.invoke({})
        return response.content

    def start_exam(self, knowledge_content: str):
        """开始考试流程"""
        print("\n" + "=" * 50)
        print("智能考试机器人 (基于LangChain)".center(40))
        print("=" * 50)
        print("\n我将根据提供的知识内容对你进行考察。")
        print(f"考试共{self.max_questions}题，每道题会有最多{self.max_follow_ups}次追问。")
        print("输入'开始'即可开始考试")
        print("输入'退出'可随时结束考试。\n")

        # 初始化对话历史
        chat_history = [
            AIMessage(content="我已准备好开始考试。请输入'开始'进行第一题。")
        ]

        while True:
            # 检查是否达到最大题目数量
            if self.question_count >= self.max_questions:
                print("\n已完成所有题目！")
                self._show_final_score()
                break

            user_input = input("\n你: ").strip()

            if user_input.lower() in ['退出', 'q', 'quit']:
                self._show_final_score()
                break

            if user_input.lower() in ['开始', '继续', 'next']:
                self._handle_new_question(knowledge_content, chat_history)
            else:
                # 验证输入有效性
                if not user_input:
                    print("请输入有效的回答内容")
                    continue
                self._handle_user_answer(knowledge_content, user_input, chat_history)

    def _handle_new_question(self, knowledge_content: str, chat_history: List):
        """处理新题目"""
        self.question_count += 1
        self.current_follow_ups = 0  # 重置当前题目的追问次数

        print(f"\n===== 第{self.question_count}/{self.max_questions}题 =====")
        # 获取新题目
        chat_history.append(HumanMessage(content="请出一道较为简单的主观题，考察对知识的理解和应用能力"))
        question = self.get_question(knowledge_content, chat_history)
        chat_history.append(AIMessage(content=question))

        print(f"考试机器人: {question}")
        self.max_possible_score += 10  # 每道题满分10分

    def _handle_user_answer(self, knowledge_content: str, user_answer: str, chat_history: List):
        """处理用户回答"""
        chat_history.append(HumanMessage(content=user_answer))

        # 打印用户回答，便于调试
        print(f"\n用户回答: {user_answer}")

        # 获取评估
        evaluation = self.evaluate_answer(knowledge_content, chat_history)

        try:
            score = float(evaluation["score"])
            feedback = evaluation["feedback"]
            need_follow_up = evaluation["need_follow_up"].lower() == '是'

            # 根据是否是追问调整分数权重
            if self.current_follow_ups > 0:
                # 追问的分数权重递减
                weight = 1.0 - (self.current_follow_ups * 0.2)
                adjusted_score = score * weight
                self.total_score += adjusted_score
                print(
                    f"\n[追问得分({self.current_follow_ups}/{self.max_follow_ups})] {adjusted_score:.1f}/{score:.1f} (权重调整后)")
            else:
                self.total_score += score
                print(f"\n评分: {score:.1f}/10")

            print(f"反馈: {feedback}")
            print(f"累计得分: {self.total_score:.1f}/{self.max_possible_score}")

            if need_follow_up and self.current_follow_ups < self.max_follow_ups:
                self.current_follow_ups += 1
                self._ask_follow_up(chat_history)
            else:
                print(f"\n本题结束，当前进度: {self.question_count}/{self.max_questions}题")
                print("输入'继续'进入下一题，或输入'退出'结束考试")

        except Exception as e:
            print(f"\n处理评分结果失败: {str(e)}")
            # 提供备选方案，手动继续
            print("请输入'继续'进入下一题")

    def _ask_follow_up(self, chat_history: List):
        """提出追问问题"""
        follow_up_question = self.get_follow_up_question(chat_history)
        chat_history.append(AIMessage(content=follow_up_question))
        print(f"\n考试机器人 [追问 {self.current_follow_ups}/{self.max_follow_ups}]: {follow_up_question}")

    def _show_final_score(self):
        """显示最终得分"""
        print("\n" + "=" * 50)
        if self.question_count > 0:
            percentage = (self.total_score / self.max_possible_score) * 100 if self.max_possible_score > 0 else 0
            print(f"\n考试结束！共完成 {self.question_count} 道题")
            print(f"最终得分: {self.total_score:.1f}/{self.max_possible_score} ({percentage:.1f}%)")

            if percentage >= 80:
                print("成绩优秀！你对知识点的掌握非常扎实。")
            elif percentage >= 60:
                print("成绩良好！对主要内容有较好理解，但还有提升空间。")
            else:
                print("需要加强学习！建议回顾相关知识点。")
        else:
            print("本次未完成任何题目。")
        print("=" * 50 + "\n")


if __name__ == "__main__":
    # 配置信息
    CONFIG = {
        "api_key": "sk-faa84a6e39ed4a78a90b49b8fb811bfc",
        "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
        "file_path": r"D:\桌面\竞赛\挑战杯+人工智能\第3章 数据获取.docx",  # 支持docx/txt文件
        "max_questions": 5  # 最大题目数量
    }

    try:
        # 创建考试机器人实例（基于LangChain）
        exam_bot = SmartExamBot(
            api_key=CONFIG["api_key"],
            base_url=CONFIG["base_url"],
            max_questions=CONFIG["max_questions"]
        )

        # 读取知识内容
        knowledge_content = exam_bot.read_file_content(CONFIG["file_path"])
        print(f"成功加载知识内容，长度: {len(knowledge_content)} 字符")
        print("知识内容预览:", knowledge_content[:200] + "...")  # 显示内容预览

        # 开始考试
        exam_bot.start_exam(knowledge_content)

    except Exception as e:
        print(f"程序运行出错: {str(e)}")
    finally:
        input("按回车键退出...")